<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace JfbCache;

use Hyperf\Cache\Annotation\Cacheable;
use Hyperf\Cache\Annotation\CacheAhead;
use Hyperf\Cache\Annotation\CacheEvict;
use Hyperf\Cache\Annotation\CachePut;
use Hyperf\Cache\Annotation\FailCache;
use Hyperf\Cache\Exception\CacheException;
use Hyperf\Cache\Helper\StringHelper;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\AbstractAnnotation;
use Hyperf\Di\Annotation\AnnotationCollector;
use JfbCache\Annotation\CacheableX;
use JfbCache\Annotation\CacheAheadX;
use JfbCache\Annotation\CacheEvictX;
use JfbCache\Annotation\CachePutX;
use JfbCache\Annotation\FailCacheX;

class AnnotationManager
{
    public function __construct(protected ConfigInterface $config, protected StdoutLoggerInterface $logger)
    {
    }

    public function getCacheableValue(string $className, string $method, array $arguments): array
    {
        /** @var CacheableX $annotation */
        $annotation = $this->getAnnotation(CacheableX::class, $className, $method);

        $key = $this->getFormattedKey($annotation->prefix, $arguments, $annotation->value);
        $group = $annotation->group;
        $ttl = $annotation->ttl ?? $this->config->get("cache.{$group}.ttl", 3600);
        $randomOffSet = $this->getRandomOffset($annotation->offset);
        $expiredTime = $annotation->expire_ttl === 0 || $annotation->expire_ttl > $ttl ? 0 : time() + $annotation->expire_ttl + $randomOffSet;

        return [$key, $ttl + $randomOffSet, $expiredTime, $group, $annotation];
    }

    public function getCacheEvictValue(string $className, string $method, array $arguments): array
    {
        /** @var CacheEvict $annotation */
        $annotation = $this->getAnnotation(CacheEvictX::class, $className, $method);

        $prefix = $annotation->prefix;
        $all = $annotation->all;
        $group = $annotation->group;
        if (! $all) {
            $key = $this->getFormattedKey($prefix, $arguments, $annotation->value);
        } else {
            $key = $prefix . ':';
        }

        return [$key, $all, $group, $annotation];
    }

    public function getCachePutValue(string $className, string $method, array $arguments): array
    {
        /** @var CachePut $annotation */
        $annotation = $this->getAnnotation(CachePutX::class, $className, $method);

        $key = $this->getFormattedKey($annotation->prefix, $arguments, $annotation->value);
        $group = $annotation->group;
        $ttl = $annotation->ttl ?? $this->config->get("cache.{$group}.ttl", 3600);
        $randomOffSet = $this->getRandomOffset($annotation->offset);
        $expiredTime = $annotation->expire_ttl === 0 || $annotation->expire_ttl > $ttl ? 0 : time() + $annotation->expire_ttl + $randomOffSet;

        return [$key, $ttl + $randomOffSet, $expiredTime, $group, $annotation];
    }

    protected function getRandomOffset(int $offset): int
    {
        if ($offset > 0) {
            return rand(0, $offset);
        }

        return 0;
    }

    protected function getAnnotation(string $annotation, string $className, string $method): AbstractAnnotation
    {
        $collector = AnnotationCollector::get($className);
        $result = $collector['_m'][$method][$annotation] ?? null;
        if (! $result instanceof $annotation) {
            throw new CacheException(sprintf('Annotation %s in %s:%s not exist.', $annotation, $className, $method));
        }

        return $result;
    }

    protected function getFormattedKey(string $prefix, array $arguments, ?string $value = null): string
    {
        return StringHelper::format($prefix, $arguments, $value);
    }
}
